#include "Sheet.h"
#include "ObjectFactory.h"
#include "AbstractObject.h"
#include "Filesystem.h"
#include "Logger.h"
#include <assert.h>
#include "LuaUtils.h"

Sheet::Sheet(Core* um, int id, std::string masterClass)
{
	LOG(logDEBUG) << "Sheet " << id << " being created with master= " << masterClass;

	_id = id;
	_um = um;
	_of = NULL;

	LOG(logDEBUG) << "Basic Lua init";
	// initialize new lua interpreter
	_L = lua_open();
	// open default libraries
	luaL_openlibs(_L);
	// add core static funcs into a new table
	lua_newtable(_L);
	static const struct luaL_Reg mylib [] = {
		// Paste these into lua interface list:
		{"RawSetInheritance", li_rawSetInheritance},
		{"RawSetCppInheritance", li_rawSetCppInheritance},
		{"RawGetProto", li_rawGetProto},
		{"RawCreateTLO", li_createTopLevelObj},
		{"RawDeleteTLO", li_deleteTopLevelObj},
		{"RawGetTLOTable", li_getTLOTAble},
		{"RawGetLuaProto", li_getLuaProto},
		{"RawCreateSheet", li_createSheet},
		{"RawDeleteSheet", li_deleteSheet},
		{"RawLogPrint", li_logPrint},
		{"RawConPrint", li_conPrint},
		{"RawQuit", li_quit},
		{"CheckNull", li_checkNull},
		{"RawGetSysArgs", li_getSysArgs},
		{NULL, NULL}  /* sentinel */
    };
	luaL_register(_L, NULL, mylib);
	// set global "corefuncs" to that table
	lua_setglobal(_L, "corefuncs");
	// load in and run core
	LOG(logDEBUG) << "Trying to load core.";
	int lcres = fs()->loadLuaChunk(_L, Core::DEFAULT_PATH, "core");
	if(lcres < 0)
	{
		LOG(logERROR) << "Could not load core!";
		return;
	}
	if(lua_pcall(_L, 0, 0, 0) != 0) // error!
	{
		LOG(logERROR) << "Core failed to run: " << std::string(getSafeLuaString(_L, -1));
		lua_settop(_L, -2);
	}
	// (core is now in the global table)
	// call core.init(sheetPtr)
	LOG(logDEBUG) << "Trying to run core init";
	getLuaCoreFunc("init", _L);
	lua_pushlightuserdata(_L, this);
	if(lua_pcall(_L, 1, 0, 0) != 0) // error!
	{
		LOG(logERROR) << "Core init error: " << std::string(getSafeLuaString(_L, -1));
		lua_settop(_L, -2);
	}
	// create and reference topLevelTable
	lua_newtable(_L);
	_topLevelTableRef = luaL_ref(_L, LUA_REGISTRYINDEX);

	// call core.setTopLevelTable(topLevelTableRef)
	getLuaCoreFunc("setTopLevelTable", _L);
	lua_rawgeti(_L, LUA_REGISTRYINDEX, _topLevelTableRef);
	if(lua_pcall(_L, 1, 0, 0) != 0) // error!
	{
		LOG(logERROR) << "Core setTopLevelTable error: " << std::string(getSafeLuaString(_L, -1));
		lua_settop(_L, -2);
	}

	// create object factory
	_of = new ObjectFactory(this);

	// Moved into core (master must be initted after sheet creation for some things to work properly)
	//// create master if one given
	//if(masterClass != "")
	//{
	//	AnyAOPtr tempMaster = createTopLevelObj(masterClass, "master");
	//	if(*tempMaster == NULL)
	//		LOG(logERROR) << "Error creating master [" << masterClass << "]";
	//	else
	//		tempMaster->init();
	//}
}

int Sheet::getId()
{
	return _id;
}

void Sheet::update(double dt)
{
	// update all top level objects
	std::map<std::string, AnyStrongAOPtr>::iterator itr;
	for(itr = _topLevelObjects.begin(); itr != _topLevelObjects.end(); ++itr)
	{
		(*itr).second->update(dt);
		//(*itr)->update(dt);
	}

	// run console commands
	for(unsigned int i = 0; i < _consoleList.size(); ++i)
	{
		if(_consoleList[i].hook != NULL && _consoleList[i].chan >= 0)
		{
			std::vector<std::string> cmds = _consoleList[i].hook->receiveMessages();
			for(unsigned int j = 0; j < cmds.size(); ++j)
			{
				//LOG(logINFO) << "Executing console command: " << cmds[j];
				execConCommand(_consoleList[i].chan, cmds[j]);
			}
		}
	}
}

ObjectFactory* Sheet::factory()
{
	return _of;
}

Core* Sheet::ultra()
{
	return _um;
}

Filesystem* Sheet::fs()
{
	return _um->getFs();
}

AnyAOPtr Sheet::createTopLevelObj(std::string className, std::string objName)
{
	AnyAOPtr ret = NULL;

	if(_topLevelObjects.count(objName) > 0)
	{
		LOG(logERROR) << "Top level object with name " << objName << " already exists.";
		return ret;
	}

	// first, create the object
	AnyStrongAOPtr temp = _of->createObject(className);
	if(*temp == NULL)
	{
		LOG(logERROR) << "Could not create top level object (some sort of error)";
		return ret;
	}

	temp->setName(objName);
	_topLevelObjects[objName] = temp;

	// Next, add this to the top level table
	lua_rawgeti(_L, LUA_REGISTRYINDEX, _topLevelTableRef);
	temp->pushSelf(_L);
	lua_setfield(_L, -2, objName.c_str());
	lua_settop(_L, -2);

	ret = temp;
	return ret;
}

AnyAOPtr Sheet::getTopLevelObj(std::string objName)
{
	AnyAOPtr ret = NULL;
	if(_topLevelObjects.count(objName) > 0)
		ret = _topLevelObjects[objName];
	return ret;
}

void Sheet::deleteTopLevelObj(std::string objName)
{
	if(_topLevelObjects.count(objName) <= 0)
	{
		LOG(logERROR) << "Cannot delete top level object " << objName << " because it does not exist.";
		return;
	}

	// clear out cpp representation
	_topLevelObjects.erase(objName);

	// clear out lua representation
	lua_rawgeti(_L, LUA_REGISTRYINDEX, _topLevelTableRef);
	lua_pushnil(_L);
	lua_setfield(_L, -2, objName.c_str());
	lua_settop(_L, -2);
}

int Sheet::addConsole(Console* hook, int channel)
{
	if(channel >= 0)
	{
		_consoleTable[channel].push_back(hook);
		ConHookStub stub;
		stub.hook = hook;
		stub.chan = channel;
		_consoleList.push_back(stub);
		return channel;
	}
	else
	{
		// find a free channel
		for(int i = 0; i < MAX_CHANNELS; ++i)
		{
			if(_consoleTable[i].size() == 0)
			{
				// RECURSION!
				return addConsole(hook, i);
			}
		}
		return -3;
	}
}

void Sheet::removeConsole(Console* hook)
{
	for(int i = 0; i < MAX_CHANNELS; ++i)
	{
		for(unsigned int j = 0; j < _consoleTable[i].size(); ++j)
			if(_consoleTable[i][j] == hook)
				_consoleTable[i][j] = NULL;
	}
	for(unsigned int i = 0; i < _consoleList.size(); ++i)
		if(_consoleList[i].hook == hook)
			_consoleList[i].hook = NULL;
}

void Sheet::consolePrint(int channel, std::string& str)
{
	//LOG(logINFO) << "Cprint: " << str;
	if(channel >= 0 && channel < MAX_CHANNELS)
	{
		for(unsigned int i = 0; i < _consoleTable[channel].size(); ++i)
			if(_consoleTable[channel][i] != NULL)
				_consoleTable[channel][i]->sendMessage(str);
	}
	else if(channel == -1)
	{
		ultra()->sendGlobalConMessage(str);
	}
}


// pushes the function from core.lua named fname onto the stack of L
// right now core is assumed to just dump everything into the global environment
void Sheet::getLuaCoreFunc(std::string fname, lua_State* L)
{
	assert(L == _L);
	lua_getglobal(L, fname.c_str());
}

lua_State* Sheet::getL()
{
	return _L;
}

// Core lua interface functions

// bool RawSetInheritance(Sheet* sh, childName, parentName)
int Sheet::li_rawSetInheritance(lua_State* L)
{
	Sheet* sh = (Sheet*)lua_touserdata(L,1);
	std::string childName(getSafeLuaString(L,2));
	std::string parentName(getSafeLuaString(L,3));
	sh->factory()->setInheritance(childName, parentName);
	lua_pushnumber(L, 1);
	return 1;
}

// bool RawSetCppInheritance(Sheet* sh, childName, parentName)
int Sheet::li_rawSetCppInheritance(lua_State* L)
{
	Sheet* sh = (Sheet*)lua_touserdata(L,1);
	std::string childName(getSafeLuaString(L,2));
	std::string parentName(getSafeLuaString(L,3));
	sh->factory()->setCppInheritanceOnly(childName, parentName);
	lua_pushnumber(L, 1);
	return 1;
}

// table RawGetProto(Sheet* sh, className)
int Sheet::li_rawGetProto(lua_State* L)
{
	Sheet* sh = (Sheet*)lua_touserdata(L,1);
	std::string className(getSafeLuaString(L,2));
	ObjectProto* p = sh->factory()->getPrototype(className);
	if(p != NULL)
		lua_rawgeti(L, LUA_REGISTRYINDEX, p->luaRef);
	else
		lua_pushnil(L);
	return 1;
}

// AnyAOPtr CreateTopLevelObj(Sheet* sh, className, objName)
int Sheet::li_createTopLevelObj(lua_State* L)
{
	Sheet* sh = (Sheet*)lua_touserdata(L,1);
	std::string className(getSafeLuaString(L,2));
	std::string objName(getSafeLuaString(L,3));
	AnyAOPtr res = sh->createTopLevelObj(className, objName);
	if(*res != NULL)
		res->pushSelf(L);
	else
		lua_pushnil(L);
	return 1;
}

// bool DeleteTopLevelObj(Sheet* sh, objName)
int Sheet::li_deleteTopLevelObj(lua_State* L)
{
	Sheet* sh = (Sheet*)lua_touserdata(L,1);
	std::string objName(getSafeLuaString(L,2));
	sh->deleteTopLevelObj(objName);
	lua_pushnumber(L,1);
	return 1;
}

// table GetTLOTable(Sheet* sh)
int Sheet::li_getTLOTAble(lua_State* L)
{
	Sheet* sh = (Sheet*)lua_touserdata(L,1);
	lua_rawgeti(L, LUA_REGISTRYINDEX, sh->_topLevelTableRef);
	return 1;
}

// table GetLuaProto(Sheet* sh, className)
int Sheet::li_getLuaProto(lua_State* L)
{
	Sheet* sh = (Sheet*)lua_touserdata(L,1);
	std::string className(getSafeLuaString(L,2));
	ObjectProto* p = sh->factory()->getPrototype(className);
	if(p != NULL)
		lua_rawgeti(L, LUA_REGISTRYINDEX, p->luaRef);
	else
		lua_pushnil(L);
	return 1;
}

// int CreateSheet(Sheet* sh, className) // creates a sheet, instantiating an object
//							  //  of classname, and registering it as a TLO
//							  //  with the name 'master'
int Sheet::li_createSheet(lua_State* L)
{
	Sheet* sh = (Sheet*)lua_touserdata(L,1);
	std::string className(getSafeLuaString(L,2));
	int ns = sh->ultra()->createSheet(className);
	lua_pushinteger(L, ns);
	return 1;
}

// bool DeleteSheet(Sheet* sh, int sheetid)
int Sheet::li_deleteSheet(lua_State* L)
{
	Sheet* sh = (Sheet*)lua_touserdata(L,1);
	int sheetId = lua_tointeger(L, 2);
	sh->ultra()->deleteSheet(sheetId);
	lua_pushinteger(L, 1);
	return 1;
}

// void logPrint(Sheet* sh, string)
int Sheet::li_logPrint(lua_State* L)
{
	Sheet* sh = (Sheet*)lua_touserdata(L,1);
	std::string ps(getSafeLuaString(L,2));
	LOG(logINFO) << ps;
	return 0;
}

int Sheet::li_conPrint(lua_State* L)
{
	Sheet* sh = (Sheet*)lua_touserdata(L,1);
	int chan = lua_tointeger(L, 2);
	std::string str(getSafeLuaString(L, 3));
	sh->consolePrint(chan, str);
	return 0;
}

int Sheet::li_quit(lua_State* L)
{
	Sheet* sh = (Sheet*)lua_touserdata(L,1);
	sh->ultra()->quit();
	return 0;
}

int Sheet::li_checkNull(lua_State* L)
{
	AbstractObject* ao = unboxAO(L, 1);
	lua_pushboolean(L, ao == NULL);
	return 1;
}

int Sheet::li_getSysArgs(lua_State* L)
{
	Sheet* sh = (Sheet*)lua_touserdata(L,1);
	Core* core = sh->ultra();
	std::vector<std::string> contexts = core->getSysArgContexts();
	lua_newtable(L);
	for(unsigned int i = 0; i < contexts.size(); ++i)
	{
		lua_newtable(L);
		std::vector<std::string> opts = core->getSysArg(contexts[i]);
		for(unsigned int j = 0; j < opts.size(); ++j)
		{
			lua_pushlstring(L, opts[j].c_str(), opts[j].size());
			lua_rawseti(L, -2, j+1);
		}
		lua_setfield(L, -2, contexts[i].c_str());
	}
	return 1;
}


Sheet::~Sheet()
{
	// TODO
}

void Sheet::execConCommand(int chan, std::string& str)
{
	lua_State* L = getL();
	getLuaCoreFunc("execConCmd", L);
	lua_pushinteger(L, chan);
	lua_pushstring(L, str.c_str());
	if(lua_pcall(L, 2, 0, 0) != 0) // error!
	{
		LOG(logERROR) << "Console command error: " << std::string(getSafeLuaString(L, -1));
		lua_settop(L, -2);
	}
}
